
/*
 * Copyright (C) 2008-2009 Archie L. Cobbs. All rights reserved.
 *
 * $Id$
 */

package org.dellroad.sidekar.datastore;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.dellroad.sidekar.Entity;
import org.dellroad.sidekar.Index;
import org.dellroad.sidekar.ListField;
import org.dellroad.sidekar.MapField;
import org.dellroad.sidekar.SetField;
import org.dellroad.sidekar.SimpleField;
import org.dellroad.sidekar.query.QueryList;
import org.dellroad.sidekar.query.QueryMap;
import org.dellroad.sidekar.query.QuerySet;
import org.dellroad.sidekar.schema.change.SchemaChange;
import org.dellroad.sidekar.schema.model.SchemaModel;
import org.dellroad.sidekar.util.SidekarException;
import org.dellroad.stuff.jibx.JiBXUtil;
import org.jibx.runtime.JiBXException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Generic support superclass for {@link Datastore} implementations.
 * This class implements the locking specified by the {@link Datastore} interface.
 */
public abstract class AbstractDatastore implements Datastore {

    protected final Logger log = LoggerFactory.getLogger(getClass());

    private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);

    private boolean open;

    @Override
    public final void datastoreOpen() {
        Lock lock = this.readWriteLock.writeLock();
        try {
            if (this.open)
                throw new IllegalStateException("already open");
            this.doDatastoreOpen();
            this.open = true;
        } finally {
            lock.unlock();
        }
    }

    /**
     * Subclass implementation of {@link #datastoreOpen}. Locking and checking of open/close state will be handled already.
     */
    protected abstract void doDatastoreOpen();

    @Override
    public final void datastoreClose() {
        Lock lock = this.readWriteLock.writeLock();
        try {
            if (!this.open)
                return;
            this.doDatastoreClose();
            this.open = false;
        } finally {
            lock.unlock();
        }
    }

    /**
     * Subclass implementation of {@link #datastoreClose}. Locking and checking of open/close state will be handled already.
     */
    protected abstract void doDatastoreClose();

    @Override
    public final SchemaModel schemaRetrieve() {
        Lock lock = this.readWriteLock.readLock();
        try {
            if (!this.open)
                throw new IllegalStateException("not open");
            return this.doSchemaRetrieve();
        } finally {
            lock.unlock();
        }
    }

    /**
     * Subclass implementation of {@link #schemaRetrieve}. Locking and checking of open/close state will be handled already.
     */
    protected abstract SchemaModel doSchemaRetrieve();

    @Override
    public final void schemaChange(SchemaChange change) {
        Lock lock = this.readWriteLock.writeLock();
        try {
            if (!this.open)
                throw new IllegalStateException("not open");
            this.doSchemaChange(change);
        } finally {
            lock.unlock();
        }
    }

    /**
     * Subclass implementation of {@link #schemaChange}. Locking and checking of open/close state will be handled already.
     */
    protected abstract void doSchemaChange(SchemaChange change);

    @Override
    public final long instanceCreate(Entity<?> entity) {
        Lock lock = this.readWriteLock.readLock();
        try {
            if (!this.open)
                throw new IllegalStateException("not open");
            return this.doInstanceCreate(entity);
        } finally {
            lock.unlock();
        }
    }

    /**
     * Subclass implementation of {@link #instanceCreate}. Locking and checking of open/close state will be handled already.
     */
    protected abstract long doInstanceCreate(Entity<?> entity);

    @Override
    public final void instanceAttach(Entity<?> entity, long id) {
        Lock lock = this.readWriteLock.readLock();
        try {
            if (!this.open)
                throw new IllegalStateException("not open");
            this.doInstanceAttach(entity, id);
        } finally {
            lock.unlock();
        }
    }

    /**
     * Subclass implementation of {@link #instanceAttach}. Locking and checking of open/close state will be handled already.
     */
    protected abstract void doInstanceAttach(Entity<?> entity, long id);

    @Override
    public final void instanceDetach(Entity<?> entity, long id) {
        Lock lock = this.readWriteLock.readLock();
        try {
            if (!this.open)
                throw new IllegalStateException("not open");
            this.doInstanceAttach(entity, id);
        } finally {
            lock.unlock();
        }
    }

    /**
     * Subclass implementation of {@link #instanceDetach}. Locking and checking of open/close state will be handled already.
     */
    protected abstract void doInstanceDetach(Entity<?> entity, long id);

    @Override
    public final boolean instanceIsAttached(Entity<?> entity, long id) {
        Lock lock = this.readWriteLock.readLock();
        try {
            if (!this.open)
                throw new IllegalStateException("not open");
            return this.doInstanceIsAttached(entity, id);
        } finally {
            lock.unlock();
        }
    }

    /**
     * Subclass implementation of {@link #instanceIsAttached}. Locking and checking of open/close state will be handled already.
     */
    protected abstract boolean doInstanceIsAttached(Entity<?> entity, long id);

    @Override
    public final <E> QuerySet<E> entityAccess(Entity<E> entity, boolean exact) {
        Lock lock = this.readWriteLock.readLock();
        try {
            if (!this.open)
                throw new IllegalStateException("not open");
            return this.doEntityAccess(entity, exact);
        } finally {
            lock.unlock();
        }
    }

    /**
     * Subclass implementation of {@link #entityAccess}. Locking and checking of open/close state will be handled already.
     */
    protected abstract <E> QuerySet<E> doEntityAccess(Entity<?> entity, boolean exact);

    @Override
    public final <T> T simpleFieldRead(Entity<?> entity, long id, SimpleField<T> field) {
        Lock lock = this.readWriteLock.readLock();
        try {
            if (!this.open)
                throw new IllegalStateException("not open");
            return this.doSimpleFieldRead(entity, id, field);
        } finally {
            lock.unlock();
        }
    }

    /**
     * Subclass implementation of {@link #simpleFieldRead}. Locking and checking of open/close state will be handled already.
     */
    protected abstract <T> T doSimpleFieldRead(Entity<?> entity, long id, SimpleField<T> field);

    @Override
    public final <T> void simpleFieldWrite(Entity<?> entity, long id, SimpleField<T> field, T value) {
        Lock lock = this.readWriteLock.readLock();
        try {
            if (!this.open)
                throw new IllegalStateException("not open");
            this.doSimpleFieldWrite(entity, id, field, value);
        } finally {
            lock.unlock();
        }
    }

    /**
     * Subclass implementation of {@link #simpleFieldWrite}. Locking and checking of open/close state will be handled already.
     */
    protected abstract <T> void doSimpleFieldWrite(Entity<?> entity, long id, SimpleField<T> field, T value);

    @Override
    public final <E> QuerySet<E> setFieldAccess(Entity<?> entity, long id, SetField<E> field) {
        Lock lock = this.readWriteLock.readLock();
        try {
            if (!this.open)
                throw new IllegalStateException("not open");
            return this.doSetFieldAccess(entity, id, field);
        } finally {
            lock.unlock();
        }
    }

    /**
     * Subclass implementation of {@link #setFieldAccess}. Locking and checking of open/close state will be handled already.
     */
    protected abstract <E> QuerySet<E> doSetFieldAccess(Entity<?> entity, long id, SetField<E> field);

    @Override
    public final <K, V> QueryMap<K, V> mapFieldAccess(Entity<?> entity, long id, MapField<K, V> field) {
        Lock lock = this.readWriteLock.readLock();
        try {
            if (!this.open)
                throw new IllegalStateException("not open");
            return this.doMapFieldAccess(entity, id, field);
        } finally {
            lock.unlock();
        }
    }

    /**
     * Subclass implementation of {@link #mapFieldAccess}. Locking and checking of open/close state will be handled already.
     */
    protected abstract <K, V> QueryMap<K, V> doMapFieldAccess(Entity<?> entity, long id, MapField<K, V> field);

    @Override
    public final <E> QueryList<E> listFieldAccess(Entity<?> entity, long id, ListField<E> field) {
        Lock lock = this.readWriteLock.readLock();
        try {
            if (!this.open)
                throw new IllegalStateException("not open");
            return this.doListFieldAccess(entity, id, field);
        } finally {
            lock.unlock();
        }
    }

    /**
     * Subclass implementation of {@link #listFieldAccess}. Locking and checking of open/close state will be handled already.
     */
    protected abstract <E> QueryList<E> doListFieldAccess(Entity<?> entity, long id, ListField<E> field);

    @Override
    public final <E> QuerySet<E> indexAccess(Entity<?> entity, long id, Index<E> index, Object... values) {
        Lock lock = this.readWriteLock.readLock();
        try {
            if (!this.open)
                throw new IllegalStateException("not open");
            return this.doIndexAccess(entity, id, index, values);
        } finally {
            lock.unlock();
        }
    }

    /**
     * Subclass implementation of {@link #indexAccess}. Locking and checking of open/close state will be handled already.
     */
    protected abstract <E> QuerySet<E> doIndexAccess(Entity<?> entity, long id, Index<E> index, Object... values);

    /**
     * Determine whether this instance is open or closed.
     *
     * @return true if open
     */
    public boolean isOpen() {
        return this.open;
    }

    /**
     * Serialize a {@link SchemaModel} into a {@link String} for easy storage.
     *
     * @see #deserializeSchemaModel
     */
    protected String serializeSchemaModel(SchemaModel schemaModel) {
        try {
            return JiBXUtil.toString(schemaModel);
        } catch (JiBXException e) {
            throw new SidekarException(e);
        } catch (IOException e) {
            throw new SidekarException(e);
        }
    }

    /**
     * De-serialize a {@link SchemaModel} previously serialized into a {@link String} by {@link #serializeSchemaModel}.
     */
    protected SchemaModel deserializeSchemaModel(String string) {
        try {
            ByteArrayInputStream input = new ByteArrayInputStream(string.getBytes("UTF-8"));
            return JiBXUtil.readObject(SchemaModel.class, input);
        } catch (JiBXException e) {
            throw new SidekarException(e);
        } catch (IOException e) {
            throw new SidekarException(e);
        }
    }
}

